#include "h264decode_gst.hpp"
#include "gst/app/gstappsink.h"
#include "gst/app/gstappsrc.h"
#include <cstring>
#include <stdexcept>
#include <sstream>
#include <iostream>

gboolean bus_call(GstBus *bus,GstMessage *msg,gpointer data)
{
  GMainLoop* loop=(GMainLoop*)data;
  gchar  *debug;
  GError *error;
  switch(GST_MESSAGE_TYPE(msg))
  {
    case GST_MESSAGE_EOS:
      std::cout<<"Get Stream EOF"<<std::endl;
      g_main_loop_quit(loop);
    break;
    case GST_MESSAGE_ERROR:

      gst_message_parse_error (msg, &error, &debug);
      g_free (debug);
      std::cerr<<"Get Stream error : "<<error->message<<std::endl;
      g_error_free (error);

      g_main_loop_quit (loop);
    break;
    default:
      gst_message_parse_error (msg, &error, &debug);
      g_free (debug);
      std::cerr<<"Get Stream error : "<<error->message<<std::endl;
      g_error_free (error);
    break;
  }
  return true;
}

void GlobalInit()
{
  gst_init(nullptr,nullptr);
}

void on_enough_appsrc(GstElement* appsrc,gpointer udata)
{
  std::cout<<"The pipeline queue is full, check CPU/GPU usage"<<std::endl;
  ((h264decode_gst*)udata)->pushDataFlag=false;
}

void on_needdata_appsrc(GstElement* appsrc,guint length,gpointer udata)
{
  ((h264decode_gst*)udata)->pushDataFlag=true;
}

std::string CreateDynamicName(const std::string & str,int val)
//用来快速创建变量名
{
  std::stringstream name;
  name<<str<<"_"<<val;
  return name.str();
}

h264decode_gst::h264decode_gst()
{
  //创建主循环
  loop=g_main_loop_new(nullptr,false);
  //创建元素
  appsrc=gst_element_factory_make("appsrc",CreateDynamicName("appsrc",ID_counter).c_str());
  queue=gst_element_factory_make("queue",CreateDynamicName("queue",ID_counter).c_str());
  h264parse=gst_element_factory_make("h264parse",CreateDynamicName("h264parse",ID_counter).c_str());
  openh264dec=gst_element_factory_make("nvh264dec",CreateDynamicName("nvh264dec",ID_counter).c_str());
  videoConvert=gst_element_factory_make("cudaconvert",CreateDynamicName("cudaconvert",ID_counter).c_str());
  cudadownload=gst_element_factory_make("cudadownload",CreateDynamicName("cudadownload",ID_counter).c_str());
  appsink=gst_element_factory_make("appsink",CreateDynamicName("appsink",ID_counter).c_str());
  //创建pipeline
  pipeline=gst_pipeline_new(CreateDynamicName("pipeline",ID_counter).c_str());
  //看看是否创建成功
  if(!appsrc || !queue || !h264parse || !openh264dec || !videoConvert || !cudadownload || !appsink || !pipeline)
    throw std::runtime_error("Can't create gstreamer element");
  bus=gst_pipeline_get_bus(GST_PIPELINE(pipeline));
  //添加bus监视函数
  busWatch=gst_bus_add_watch(bus,bus_call,(gpointer)this);

  
  g_object_set(G_OBJECT(appsrc),
               "stream-type",GST_APP_STREAM_TYPE_STREAM,
               "format",GST_FORMAT_DEFAULT,
               nullptr);

  gst_bin_add_many(GST_BIN(pipeline),appsrc,queue,h264parse,openh264dec,videoConvert,cudadownload,appsink,nullptr);
  GstCaps* caps=gst_caps_new_simple("video/x-h264",
                                    "width",G_TYPE_INT,1920,
                                    "height",G_TYPE_INT,1080,
                                    "framerate",GST_TYPE_FRACTION,50,1,
                                    "profile",G_TYPE_STRING,"baseline",
                                    "stream-format",G_TYPE_STRING,"avc3",
                                    "alignment",G_TYPE_STRING,"au",
                                    nullptr);
  if(!gst_element_link_filtered(appsrc,queue,caps))
  {
    throw std::runtime_error("Can't link appsrc to h264parse use this caps");
  }
  gst_caps_unref(caps);
  if(!gst_element_link_many(queue,h264parse,openh264dec,videoConvert,cudadownload,nullptr))
  {
    throw std::runtime_error("Can't link gstreamer elements");
  }
  caps=gst_caps_new_simple("video/x-raw",
                           "format",G_TYPE_STRING,"RGB",
                           "width",G_TYPE_INT,1920,
                           "height",G_TYPE_INT,1080,
                           "framerate",GST_TYPE_FRACTION,50,1,
                           nullptr);
  if(!gst_element_link_filtered(cudadownload,appsink,caps))
  {
    throw std::runtime_error("Can't link videoconvert to appsink");
  }
  gst_caps_unref(caps);

  //连接信号，用来控制是否要往里面加入数据
  g_signal_connect(appsrc,"enough-data",G_CALLBACK(on_enough_appsrc),(gpointer)this);
  g_signal_connect(appsrc,"need-data",G_CALLBACK(on_needdata_appsrc),(gpointer)this);

  thread_receiveImage=std::thread(&h264decode_gst::ThreadWork_receiveImage,this);
  gst_element_set_state(pipeline,GST_STATE_PLAYING);
  //启动G_Main_Loop
  thread_mainRun=std::thread(&h264decode_gst::ThreadWork_receiveImage,this);
  
  ID_counter++;
}

h264decode_gst::~h264decode_gst()
{
  pushDataFlag=false;
  gst_app_src_end_of_stream((GstAppSrc*)appsrc);
  thread_mainRun.join();
  thread_receiveImage.join();
}

void h264decode_gst::ThreadWork_receiveImage()
{
  while(true)
  {
    //获取sample，如果没有就会堵塞
    //获得的Sample内部带有互斥锁，是线程安全的
    GstSample* sample = gst_app_sink_pull_sample((GstAppSink*)appsink);
    //如果获得的为空指针，很有可能流被设置为eof
    if(sample==nullptr)
    {
      using namespace std::chrono_literals;
      if(gst_app_sink_is_eos((GstAppSink*)appsink))
        return;
      else
      {
        std::this_thread::sleep_for(1s);
        continue;
      }
    }
    //从sample中获得缓存
    GstBuffer* buffer=gst_sample_get_buffer(sample);
    if(buffer!=nullptr)
    {
      //获取buffer中的mem数量
      guint buffer_num=gst_buffer_n_memory(buffer);
      //遍历提取
      for(int i=0;i<buffer_num;i++)
      {
        //获取图像内存和大小
        GstMemory* mem=gst_buffer_peek_memory(buffer,i);
        gsize size=gst_memory_get_sizes(mem,nullptr,nullptr);
        {
          std::lock_guard<std::mutex> lck(mutResData);
          {
            //把帧号取出
            std::lock_guard<std::mutex> cck(mutFrameQueue);
            if(frameQueue.size()==0)
              throw std::runtime_error("FrameQueue error");
            imageData.FrameID=frameQueue.front();
            frameQueue.pop();
          }
          imageData.data=new unsigned char[size];
          memcpy(imageData.data,mem,size);
          imageData.data_size=size; 
          imageData.width=1920;
          imageData.height=1080;
          imageData.TimeStamp=0;
          funcHandleData(imageData);
          delete [] imageData.data;
        }
        //通知结果处理线程启动
        resReady=true;
        condRes.notify_one();
      }
    }
    //释放内存，这里不要释放buffer和memory,都是由sample进行管理的
    gst_sample_unref(sample);
  }
}

void h264decode_gst::ThreadWork_mainRun()
{
  g_main_loop_run(loop);
  //播放结束后将播放状态设为空
  gst_element_set_state(pipeline,GST_STATE_NULL);
  //这里是资源回收
  gst_object_unref(GST_OBJECT(pipeline));
  g_source_remove(busWatch);
  g_main_loop_unref(loop);
  loop=nullptr;
}

void h264decode_gst::InputNewImage(unsigned char* input_buf,std::size_t bufsize,long frameID)
//这个设计方式意味着输入和输出不在一个线程中完成
{
  if(!pushDataFlag)
    return;
  //申请一个新的缓存
  GstBuffer* buffer=gst_buffer_new();
  //分配内存
  GstMemory* memory=gst_allocator_alloc(nullptr,bufsize,nullptr);
  //获得数据写入的位置
  GstMapInfo info;
  if(!gst_memory_map(memory,&info,GST_MAP_WRITE))
    throw std::runtime_error("Can't get Gstreamer memory");
  memcpy(info.data,input_buf,bufsize);
  gst_memory_unmap(memory,&info);
  //将memory添加到buffer中
  gst_buffer_append_memory(buffer,memory);
  //发送buffer
  if(gst_app_src_push_buffer((GstAppSrc*)appsrc,buffer)==GST_FLOW_OK)
  //如果发送成功就队列里加一个帧号
  {
    std::lock_guard<std::mutex> lck(mutFrameQueue);
    frameQueue.push(frameID);
  }
}

ImageType h264decode_gst::GetNewImage()
{
  std::unique_lock<std::mutex> lk(mutResData);
  condRes.wait(lk,[this]{return resReady.load();});
  //这一步也就把data指针传递到外面去
  ImageType newImage=imageData;
  newImage.width=1920;
  newImage.height=1080;
  newImage.TimeStamp=0;
  resReady=false;
  return newImage;
}
